<?php

namespace Cframe;
use Cframe\Methods\ModelMethod;
use PDO;

abstract class Model extends ModelMethod{
    private $tablename = '';
    private $sql = '';
    private $pre = '';
    private $pdo = '';
    private $condition = '';
    private $fields = '';
    private $field = '';
    private $fields_array = array();
    private $error_info = '';
    private $alise = '';
    protected $extends = '';
    protected $createtime = '';
    protected $updatetime = false;
    protected $is_cache = true;
    public function __construct($dbname = ''){
        $this->connect();
        if (empty($dbname)) {
            $dbname = get_called_class();
            $dbname = explode("\\",$dbname);
            if (is_array($dbname)){
                $dbname = lcfirst(end($dbname));
            }
        }
        $table = $this->table($this->pre .$dbname);
        $this->tablename = $table;
        $this->getAllField();
        $this->init();
    }

    protected function init(){}

    private function table($tbname,$alise = ''){
        if (empty($alise)){
            if (strstr($tbname,' ')){
                $tb = explode(" ",$tbname);
                $tb[0] = "`{$tb[0]}`";
                $alise = $tb[1];
                $str = implode(" ",$tb);
            }else{
                $str = "`{$tbname}`";
            }
        }else{

            $str = "`{$tbname}` {$alise}";
        }
        $this->alise = $alise;
        return $str;
    }

    private function connect()
    {
        $config = $this->config();
        $connection = $config[0];
        $config = end($config);
        if (!class_exists("PDO")) {
            throw new \Exception("数据错误");
        }
        try {
            $this->pdo = new \PDO($connection, $config['user'], $config['password']);
        } catch (\PDOException $e) {
            throw new \PDOException($e->getMessage());
        }
    }

    private function config()
    {
        $config = Config::get('database');
        $this->pre = $config['prefix'];
        $connect = array("{$config['type']}:dbname={$config['dbname']};host={$config['host']};port={$config['port']};charset={$config['charset']};", $config);
        return $connect;
    }

    private function getAllField(){
        $sql = 'DESCRIBE ' . $this->tablename;
        $result = $this->query($sql);
        $fields = array();
        $temp = array();
        foreach ($result as $value) {
            if ($value['Key'] != "PRI") {
                $fields[$value['Field']] = $this->con_field($value['Field']);
            }
            $value2 = $this->conInfo($value);
            if (is_array($value) && $value['Key'] == "PRI") {
                continue;
            } else {
                if ($value['Field'] == $this->createtime){
                    $value2 = time();
                }
                $temp[$value['Field']] = $value2;

            }
        }
        $this->fields = $fields;
        $this->fields_array = $temp;
    }

    private function getFieldInfo($fieldType = ''){
        if (empty($fieldType)){
            return ;
        }
        if (strpos($fieldType,"int")){

        }
    }

    public function query($sql = '',$is_all = true,array $data = array()){
        if (empty($sql)){
            $sql = $this->sql;
        }
        if ($this->is_cache){
            $tabname = trim($this->tablename,'`');
            $data = Cache::load("Db.".$tabname .'.'. md5($sql));
            if (!empty($data)){
                return $data;
            }
        }
        $pdo = $this->pdo->prepare($sql);
        $error = $pdo->execute($data);
        if (APP_DEBUG){
            Log::Sql($sql);
        }
        if ($error === false){
            $this->error_info = $this->pdo->errorInfo();
            if (APP_DEBUG){
                Log::SqlError($this->error_info);
            }
        }
        if ($is_all){
            $data =  $pdo->fetchAll(PDO::FETCH_ASSOC);
        }else{
            $data = $pdo->fetch(PDO::FETCH_ASSOC);
        }
        if (!empty($data)) {
            $data = $this->convertData($data);
        }
        if ($this->is_cache){
            $tabname = trim($this->tablename,'`');
            Cache::set("Db.".$tabname."." . md5($sql),$data);
        }
        return $data;
    }

    public function fastSql(array $convert = array(),array $data = array(),$return_data = false){
        if (empty($convert)){
            return false;
        }
        $type = isset($convert['type'])?$convert['type']:'select';
        $where = isset($convert['where'])?$convert['where']:'';
        $order = isset($convert['order'])?$convert['order']:'';
        $limit = isset($convert['limit'])?$convert['limit']:'';
        $field = isset($convert['field'])?$convert['field']:'';
        $this->data($data);
        $this->where($where)->order($order)->limit($limit)->filed($field)->type($type);
        $this->sql = implode(' ',$type);
        if ($return_data){
            if (in_array(strtolower($type),["update","insert","delete"])){
                return $this->exec();
            }
            return $this->query();
        }
        return $this;
    }

    private function filpFieldArray(array $data = array()){
        $key = array_keys($data);
        if (is_numeric($key[0])){
            $tpl = $this->fields_array;
            foreach($data as $key=> $value){
                $data[$key] = array_merge($tpl,$value);
            }
            return $data;
        }else{
            return array_merge($this->fields_array,$data);
        }
    }

    private function conInfo($data){
        if ($data['Key'] == "PRI"){
            return $data;
        }
        if (strcmp($data['Null'] , "Yes")){
            return NULL;
        }else{
            return $data['Default'];
        }

        $type = explode("(",$data['Type']);
        $type = current($type);
        if (in_array($type,["varchar","char","text","tinytext",'longtext','mediumtext'])){
            return '';
        }

        if (in_array($type,["int","tinyint","mediumint","smallint",'bigint','double','float','dicimal','real'])){
            return '';
        }
    }

    public function exec($sql = ''){
        if (empty($sql)){
            $sql = $this->sql;
        }
        $pdo = $this->pdo->exec($sql);
        if (APP_DEBUG){
            Log::Sql($sql);
        }
        if ($pdo == 0){
            $this->error_info = $this->pdo->errorInfo();
            if (APP_DEBUG){
                Log::SqlError($this->error_info);
            }
        }
        return $pdo;
    }

    public function getError(){
        return $this->error_info;
    }

    public function convertData(array $data = array()){
        $fileds = array();
        $key1 = array_keys($this->fields_array);
        foreach ($key1 as $key){
            $keys = explode("_",$key);
            if (is_array($keys)){
                $keys = array_map(function ($m){
                    $m = ucfirst($m);
                    return $m;
                },$keys);
                $keys = implode('',$keys);
            }else{
                $keys = ucfirst($key);
            }
            $function = "get".$keys.'Var';
            if (method_exists($this,$function)){
                $fileds[$key] = $function;
            }
        }
        foreach ($data as $key => $v){
            if (is_numeric($key)){
                foreach ($v as $name => $value){
                    if (isset($fileds[$name])){
                        if (method_exists($this,$fileds[$name])) {
                            $data[$key][$name] = call_user_func([$this, $fileds[$name]], $value);
                        }
                    }
                }
            }else{
                if (isset($fileds[$key])){
                    if (method_exists($this,$fileds[$key])) {
                        $data[$key] = call_user_func([$this, $fileds[$key]], $v);
                    }
                }
            }
        }
        return $data;
    }

    public function select(array $convert = array()){
        $where = isset($convert['where'])?$convert['where']:'';
        $order = isset($convert['order'])?$convert['order']:'';
        $limit = isset($convert['limit'])?$convert['limit']:'';
        $field = isset($convert['field'])?$convert['field']:'';
        $type = "select";
        $this->where($where)->order($order)->limit($limit)->filed($field)->type($type);
        $this->sql = $sql = implode(' ',$type);
        return $this->query($sql);
    }

    public function find(array $convert = array()){
        $where = isset($convert['where'])?$convert['where']:'';
        $limit = '1';
        $field = isset($convert['field'])?$convert['field']:'';
        $type = "select";
        $this->where($where)->limit($limit)->filed($field)->type($type);
        $this->sql = $sql = implode(' ',$type);
        return $this->query($sql,false);
    }

    public function insert(array $convert = array()){

        $type = "insert";
        if (!isset($this->condition['data']) || empty($this->condition['data'])){
            if (!empty($convert)){
                $this->data($convert);
            }else{
                return $this;
            }
        }
        $this->type($type);
        $this->sql = $sql = implode(' ',$type);
        if ($this->is_cache){
            $tabname = trim($this->tablename,'`');
            Cache::del("Db.".$tabname,'',true);
        }
        return $this->exec($sql);
    }

    public function update(array $convert = array(),array $data = array()){
        $where = isset($convert['where'])?$convert['where']:'';
        $type = "update";
        if (!isset($this->condition['data']) || empty($this->condition['data'])){
            if (!empty($data)){
                $this->data($data,true);
            }else{
                return $this;
            }
        }
        $this->where($where)->type($type);
        $this->sql = $sql = implode(' ',$type);
        if ($this->is_cache){
            $tabname = trim($this->tablename,'`');
            Cache::del("Db.".$tabname,'',true);
        }
        return $this->exec($sql);
    }

    public function cache($bool = false){
        $this->is_cache = $bool;
    }

    public function counts($field = "1"){
        $field = $field == 1 ? 'count(*)' : 'count('.$this->con_field($field).')';
        $type = "select";
        $this->field = $field;
        $this->type($type);
        $this->sql = $sql = implode(' ',$type);
        return $this->query($sql,false);
    }

    public function delete(array $convert = array()){
        $where = isset($convert['where'])?$convert['where']:'';
        $type = "delete";
        $this->where($where)->type($type);
        $this->sql = $sql = implode(' ',$type);
        if ($this->is_cache){
            $tabname = trim($this->tablename,'`');
            Cache::del("Db.".$tabname,'',true);
        }
        return $this->exec($sql);
    }

    public function data(array $data = array(),$is_update = false){
        if (empty($data)){
            return $this;
        }
        $data = $this->filpFieldArray($data);
        $key = array_keys($data);
        if ($is_update){
            $string = "";
            foreach ($data as  $key => $value) {
                if (!$this->updatetime && !empty($this->createtime)){
                    if ($key == $this->createtime) {
                        continue;
                    }
                }
                if (!empty($value)) {
                    $key = $this->con_field($key);
                    $string .= "$key = '{$value}',";
                }
            }
            $string = rtrim($string,',');
            $this->condition['data'] = $string;
            return $this;
        }

        if (is_numeric($key[0])) {
            $string = '';
            foreach ($data as $key => $value) {
                $string .= "(";
                foreach ($value as  $value2) {
                    $string .= "'{$value2}',";
                }
                $string = rtrim($string,',');
                $string .= "),";
            }
            $string = rtrim($string,',');
        }else{
            $string = "(";
            foreach ($data as  $value) {
                $string .= "'{$value}',";
            }
            $string = rtrim($string,',');
            $string .= ")";
        }
        $this->condition['data'] = $string;
        return $this;
    }

    private function type(&$types)
    {
        if (empty($types)){
            return '';
        }
        if (is_string($types)) {
            switch (strtolower($types)) {
                case 'select':
                    $type = array("SELECT", $this->field, "FROM", $this->tablename);
                    if (isset($this->condition['where'])){
                        $type = array_merge($type,$this->condition['where']);
                    }
                    if (isset($this->condition['order'])){
                        $type = array_merge($type,$this->condition['order']);
                    }
                    if (isset($this->condition['limit'])){
                        $type = array_merge($type,$this->condition['limit']);
                    }
                    break;
                case 'update':
                    $type = array("UPDATE", $this->tablename, "SET");
                    $type[] = $this->condition['data'];
                    if (isset($this->condition['where'])){
                        $type = array_merge($type,$this->condition['where']);
                    }
                    break;
                case "insert":
                    $type = array("INSERT", "INTO", $this->tablename, "(".implode(',',$this->fields).")", "VALUES");
                    $type[] = $this->condition['data'];
                    break;
                case "delete":
                    $type = array("DELETE", "FROM", $this->tablename);
                    if (isset($this->condition['where'])){
                        $type = array_merge($type,$this->condition['where']);
                    }
                    break;
            }
            $types = $type;
            $this->condition = [];
            $this->field = '';
            //$this->fields = [];
        }
    }

    public function where($where)
    {
        if (empty($where)){
            return $this;
        }
        if (is_string($where)) {
            $where = array("WHERE", $where);
        } elseif (is_array($where)) {
            $temp = array("WHERE");
            foreach ($where as $k => $v) {
                if (strtolower($k) == "or") {
                    foreach ($where[$k] as $k => $v) {
                        $this->con_where($temp, $v, $k);
                    }
                    $type = '';
                    $func = array_shift($temp);
                    break;
                } else {
                    $this->con_where($temp, $v, $k);
                }
            }
            if (!isset($func)){
                $func = array_shift($temp);
            }
            $where = array("WHERE");
            if (isset($type)){
                $where[] = implode(" OR ",$temp);
            }else{
                $where[] = implode(" AND ",$temp);
            }
        }
        $this->condition["where"] = $where;
        return $this;
    }

    public function filed($field = ""){
        if (empty($field)){
            $this->field = '*';
            return $this;
        }
        $fields = '';
        if(is_array($field)){
            foreach ($field as $key=>$val){
                if (is_numeric($key)){
                    $fields .= $this->con_field($val) . ',';
                    continue;
                }
                $fields .= $this->con_field($val) . " AS {$val}";
            }
        }else{
            if (strpos($field,'(') !== false) {
                $fields = $this->con_field($field);
            }
        }
        $this->field = $fields;
        return $this;
    }

    public function order($order){
        if (empty($order)){
            return $this;
        }
        if (is_string($order)){
            $order = array("ORDER BY",$order);
        }elseif (is_array($order)){
            $type = array_keys($order)[0];
            $order = $this->con_field($order[$type]) . " ".strtoupper($type);
            $order = array("ORDER BY",$order);
        }
        $this->condition['order'] = $order;
        return $this;
    }

    public function limit($limit){
        if (empty($limit)){
            return $this;
        }
        if (is_string($limit)){
            $limit = array("LIMIT",$limit);
        }elseif (is_array($limit)){
            $limit = implode(',',array_values($limit));
            $limit = array("LIMIT",$limit);
        }
        $this->condition['limit'] = $limit;
        return $this;
    }

    private function con_where(&$temp, $v, $k)
    {
        $k = self::con_field($k);
        if (is_array($v)) {
            // 返回类型
            $type = array_shift($v);
            switch (strtolower($type)) {
                // 如果是in
                case "in":
                case "not in":
                    $value = implode(",", $v);
                    $value = $this->con_string($value);
                    $value = "({$value})";
                    break;
                // 如果是like
                case "like":
                case "not like":
                    $value = "%" . $v[0] . "%";
                    $value = $this->con_string($value);
                    break;
                case "between":
                case "not between":
                    $value = implode(",", $v);
                    $value = $this->con_string($value);
                    $value = str_replace(",", " AND ", $value);
                    break;
                // 如果是 eq neq
                default:
                    $type = $this->key_con($type);
                    $value = $this->con_string($v[0]);
                    break;
            }
            $type = strtoupper($type);
            $k .= " " . $type . " " . $value;

        } else {
            $k .= " = ";
            $v = $this->con_string($v);
            $k .= " " . $v;
        }
        array_push($temp, $k);
    }

    public function getLastsql(){
        return $this->sql;
    }

    public function __destruct(){
        $this->pdo = null;
    }

}